package com.lvy.hczn.front.business.service.impl;

import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.lvy.hczn.front.business.common.CacheConstants;
import com.lvy.hczn.front.business.common.Constants;
import com.lvy.hczn.front.business.service.WarehousingEntryService;
import com.lvy.hczn.front.common.constant.RedisConstants;
import com.lvy.hczn.front.common.core.domain.BaseModel;
import com.lvy.hczn.front.common.core.domain.entity.SysDictData;
import com.lvy.hczn.front.common.core.text.Convert;
import com.lvy.hczn.front.common.utils.DictUtils;
import com.lvy.hczn.front.system.service.GoodsBoxSpecService;
import com.lvy.hczn.front.system.service.SpuService;
import com.lvy.hczn.front.common.constant.MqConstants;
import com.lvy.hczn.front.common.utils.ShiroUtils;
import com.lvy.hczn.front.system.domain.business.*;
import com.lvy.hczn.front.business.dto.Param;
import com.lvy.hczn.front.business.dto.SortFrmLoseDto;
import com.lvy.hczn.front.business.mapper.GoodsBoxDetailMapper;
import com.lvy.hczn.front.business.mapper.GoodsBoxMapper;
import com.lvy.hczn.front.business.service.GoodsBoxDetailService;
import com.lvy.hczn.front.business.service.SpuSortRecordService;
import com.lvy.hczn.front.business.service.WcsService;
import com.lvy.hczn.front.common.core.redis.RedisCache;
import com.lvy.hczn.front.common.exception.UtilException;
import com.lvy.hczn.front.system.service.WarehouseService;
import com.lvy.hczn.mq.rocket.ParamInfo;
import com.lvy.hczn.mq.rocket.ProducerUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.ModelMap;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

@Slf4j
@Service
@Transactional(rollbackFor = Exception.class)
public class GoodsBoxDetailServiceImpl extends ServiceImpl<GoodsBoxDetailMapper, GoodsBoxDetail> implements GoodsBoxDetailService {

    @Autowired
    private GoodsBoxDetailMapper goodsBoxDetailMapper;

    @Autowired
    private WcsService wcsService;

    @Autowired
    private GoodsBoxMapper goodsBoxMapper;

    @Autowired
    private RedisCache redisCache;

    @Autowired
    private SpuSortRecordService spuSortRecordService;

    @Autowired
    private ProducerUtil producerUtil;

    @Autowired
    private WarehouseService warehouseService;

    @Autowired
    private SpuService spuService;

    @Autowired
    private WarehousingEntryService warehousingEntryService;

    @Autowired
    private GoodsBoxSpecService goodsBoxSpecService;

    /**
     * 根据原料查询最先入库的原料
     *
     * @param spuId
     * @return
     * @author yw
     * @date 2022-04-17 16:43:22
     */
    @Override
    public List<GoodsBoxDetail> selectBySpuId(String spuId) throws Exception {
        if (StrUtil.isEmpty(spuId)) {
            throw new UtilException("参数为空");
        }
        return goodsBoxDetailMapper.selectBySpuId(spuId);
    }

    /**
     * spuId list查询
     *
     * @param list
     * @return
     * @author yw
     * @date 2022-04-17 16:43:22
     */
    @Override
    public List<GoodsBoxDetail> selectBySpuIdList(List<String> list, String clientId) throws Exception {
        if (list == null || list.isEmpty()) {
            throw new UtilException("参数为空");
        }
        return goodsBoxDetailMapper.selectBySpuIdList(list, clientId);
    }

    /**
     * 同一供应商，同一批次的同一商品合并，切合并箱子内不能包含其他供应商的相同品类
     * （如：合并A供应商的苹果，且是合并A供应商的同一批次的苹果，且要合并入库
     * 的箱子中，不能含有其他供应商的苹果这个品类或者人工用其他方式物理隔开），且是操作人员触发一次，合并一箱
     * 每次请求，只合并满一箱的N个原料箱，合箱将停止所有操作，专注合箱
     *
     * @return
     * @author yw
     * @date 2022-05-10 13:51:12
     */
    @Override
    public synchronized List<Object> selectMergeBox(GoodsBox goodsBox, String clientId, boolean isDownload) throws Exception {

        List<Object> objectList = redisCache.getList(CacheConstants.MERGE_BOX_DETAIL_LIST + clientId, 0, -1);
        if (objectList != null && !objectList.isEmpty()) {
            return objectList;
        }

        if (StrUtil.isEmpty(clientId)) {
            throw new UtilException("仓库编号信息为空");
        }
        if (goodsBox == null) {
            throw new UtilException("参数为空");
        }
        Boolean signal = (Boolean) redisCache.get(CacheConstants.REDIS_KEY_WCS_SIGNAL);
        if (signal == null) {
            throw new UtilException("已与wcs断开连接，请稍后重试");
        }
        //todo 在途的或者已全部发到wcs的且还没出库到达缓存位的怎么处理
        //有正在或者待出库以及缓存位有出库且不确定是否入库的箱子，不能进行合箱
        //正出库
        Long sizeOfDownload = redisCache.getListSize(CacheConstants.SORT_TASK_DOWNLOAD + clientId);
        //待出库
        Long sizeOfDownloadLast = redisCache.getListSize(CacheConstants.SORT_TASK_DOWNLOAD_LAST + clientId);
        //到达缓存位
        Long sizeOfInBufferPos = redisCache.getListSize(CacheConstants.IN_BUFFER_POS + Constants.TYPE_WCS_RESPONSE + clientId);
        if (sizeOfDownload != null && sizeOfDownload > 0) {
            throw new UtilException("有分拣正出库货箱，不能进行合箱操作");
        } else if (sizeOfDownloadLast != null && sizeOfDownloadLast > 0) {
            throw new UtilException("有分拣待出库货箱，不能进行合箱操作");
        } else if (sizeOfInBufferPos != null && sizeOfInBufferPos > 0) {
            throw new UtilException("到达缓存位有需处理货箱，不能进行合箱操作");
        }

        String remainingPercentage = DictUtils.getDictValue(com.lvy.hczn.front.common.constant.Constants.SYS_DICT_MERGE_TYPE, com.lvy.hczn.front.common.constant.Constants.SYS_DICT_MERGE_REMAINING_PERCENTAGE);
        BigDecimal defaultNum = new BigDecimal(0.15);
        if (!StrUtil.isEmpty(remainingPercentage) && NumberUtil.isNumber(remainingPercentage)) {
            defaultNum = new BigDecimal(remainingPercentage);
        }

        if (goodsBox.getWeight() == null || goodsBox.getWeight().compareTo(BigDecimal.ZERO) < 1) {
            //货箱内物品重量小于货箱能放的总重量的百分之15时合箱
            goodsBox.setWeight(Constants.GOODS_BOX_MAX_WEIGHT.multiply(defaultNum).setScale(2, RoundingMode.HALF_UP));
        }
        if (goodsBox.getVolume() == null || goodsBox.getVolume().compareTo(BigDecimal.ZERO) > 0) {
            //货箱内物品小于货箱体积百分之15时合箱
            goodsBox.setVolume(BigDecimal.valueOf(Constants.GOODS_BOX_VOLUME).multiply(defaultNum));
        }

        //货箱查询
        goodsBox.setClientId(clientId);
        //获取查询箱数，测试从缓存取
        Integer boxNum = redisCache.getCacheObject("merge:box:num");
        if (boxNum != null && boxNum > 0) {
            goodsBox.setBoxNumber(boxNum);
        } else {
            goodsBox.setBoxNumber(3);
        }
        List<GoodsBox> list = goodsBoxMapper.selectMergeBox(goodsBox);

        if (list.isEmpty()) {
            throw new UtilException("无需合并货箱");
        }
        List<GoodsBoxDetail> boxDetailList = new ArrayList<>();
        for (GoodsBox box : list) {
            GoodsBoxDetail item = new GoodsBoxDetail();
            item.setBoxCode(box.getBoxCode());
            List<GoodsBoxDetail> detailList = goodsBoxDetailMapper.selectBoxDetailByBoxCode(item);
            if (!detailList.isEmpty()) {
                boxDetailList.addAll(detailList);
            }
        }
        if (boxDetailList.isEmpty()) {
            throw new UtilException("无相关详情");
        }
        //todo 合箱限制品类跟重量，防止合箱物品太多而去翻找，最好是四到六个物品或者更少，
        //默认，只要spu不一样，则就是不同的商品，合箱时，用袋子隔开，或者重新出库一个货箱
        LinkedHashMap<String, List<GoodsBoxDetail>> map = boxDetailList.stream().collect(Collectors.groupingBy(GoodsBoxDetail::getActTemperatureCode, LinkedHashMap::new, Collectors.toList()));
        String percent = DictUtils.getDictValue(com.lvy.hczn.front.common.constant.Constants.SYS_DICT_MERGE_TYPE, com.lvy.hczn.front.common.constant.Constants.SYS_DICT_MERGE_TYPE_PERCENT);
        BigDecimal baseNum = Constants.HALF_PERCENT;
        if (!StrUtil.isEmpty(percent) && NumberUtil.isNumber(percent)) {
            baseNum = new BigDecimal(percent);
        }

        /**查询N个货箱，根据温区分组，校验温区内的货箱，是否可以合箱到剩余重量体积最小的货箱内boxDetailList.get(0)
         * 若是可以合，则下架能合的货箱+boxDetailList.get(0)的货箱，其他货箱过滤掉
         * eg.下十个货箱，根据货箱重量体积排序，然后计算十个货箱，有多少个可以合箱到一起
         * **/

        List<Object> resultList = new ArrayList<>();
        List<Object> mergeList = new ArrayList<>();
        try {
            //计算下架货箱
            calculateDownLoadBox(map, baseNum, clientId, resultList);
        } catch (Exception e) {
            throw new UtilException(e.getMessage());
        } finally {
            if (!resultList.isEmpty()) {
                Object object = redisCache.getListIndex(CacheConstants.MERGE_BOX + clientId, 0);
                for (Object o : resultList) {
                    GoodsBoxDetail goodsBoxDetail = JSON.parseObject(JSON.toJSONString(o), GoodsBoxDetail.class);
                    if (object != null && goodsBoxDetail.getBoxCode().equals(String.valueOf(object))) {
                        goodsBoxDetail.setMergeFlag(true);
                    }
                    mergeList.add(goodsBoxDetail);
                }
                redisCache.setAllListOfRight(CacheConstants.MERGE_BOX_DETAIL_LIST + clientId, mergeList);
            }
        }


        return mergeList;
    }

    /**
     * 计算下架货箱
     *
     * @param map
     * @param baseNum
     * @param clientId
     * @param resultList
     * @return void
     * @author yw
     * @date 2023-11-05 19:21:01
     */
    public synchronized void calculateDownLoadBox(LinkedHashMap<String, List<GoodsBoxDetail>> map, BigDecimal baseNum, String clientId, List<Object> resultList) throws Exception {
        boolean flag = false;
        for (Map.Entry<String, List<GoodsBoxDetail>> detailEntry : map.entrySet()) {
            boolean isBig = checkBoxSizeOfBig(detailEntry.getValue().get(0));
            //合箱后的最大重量,合箱的重量和体积减半，防止物品或者分类太多不好分拣
            //体积区分大小货箱算
            BigDecimal maxWeight;
            BigDecimal maxVolume;
            if (isBig) {
                maxWeight = Constants.GOODS_BOX_MAX_WEIGHT.divide(BigDecimal.valueOf(Constants.WCS_WEIGHT_UNIT_COEFFICIENT), 0, RoundingMode.HALF_UP).multiply(baseNum).setScale(2, RoundingMode.HALF_UP);
                maxVolume = BigDecimal.valueOf(Constants.GOODS_BOX_VOLUME).multiply(baseNum).setScale(2, RoundingMode.HALF_UP);
            } else {
                maxWeight = Constants.GOODS_BOX_MIN_WEIGHT.divide(BigDecimal.valueOf(Constants.WCS_WEIGHT_UNIT_COEFFICIENT), 0, RoundingMode.HALF_UP).multiply(baseNum).setScale(2, RoundingMode.HALF_UP);
                maxVolume = BigDecimal.valueOf(Constants.GOODS_BOX_VOLUME_SMALL).multiply(baseNum).setScale(2, RoundingMode.HALF_UP);
            }

            //出库货箱列表
            List<GoodsBoxDetail> goodsBoxDetailList = new ArrayList<>();
            LinkedHashMap<String, List<GoodsBoxDetail>> boxCodeMap = detailEntry.getValue().stream().collect(Collectors.groupingBy(GoodsBoxDetail::getBoxCode, LinkedHashMap::new, Collectors.toList()));
            //同一温区，大于两个货箱，再合箱
            if (boxCodeMap.size() > 1) {
                BigDecimal volume = BigDecimal.ZERO;
                BigDecimal weight = BigDecimal.ZERO;
                Map<String, GoodsBoxDetail> boxMap = new HashMap<>();
                //每个货箱的重量，体积相加
                for (Map.Entry<String, List<GoodsBoxDetail>> boxCodeEntry : boxCodeMap.entrySet()) {
                    //若是有一个物品不符，则不合箱
                    boolean mergeFlag = false;
                    for (GoodsBoxDetail goodsBoxDetail : boxCodeEntry.getValue()) {
                        if (weight.compareTo(maxWeight) <= 0 && volume.compareTo(maxVolume) <= 0) {
                            //N个货箱存在相同spu，且保质期不一样，则不可以合箱
                            if (boxMap.containsKey(goodsBoxDetail.getSpuId())) {
                                GoodsBoxDetail detail = boxMap.get(goodsBoxDetail.getSpuId());
                                if (detail.getSkuCode().equals(goodsBoxDetail.getSkuCode()) && goodsBoxDetail.getShelfLife() != null && !detail.getShelfLife().isEqual(goodsBoxDetail.getShelfLife())) {
                                    mergeFlag = true;
                                    break;
                                }
                            }
                        } else {
                            flag = true;
                            break;
                        }
                    }
                    if (flag) {
                        break;
                    }
                    //货箱对比完毕后，当前货箱符合，则置到出库列表
                    if (!mergeFlag) {
                        Map<String, GoodsBoxDetail> mapSpu = boxCodeEntry.getValue().stream().collect(Collectors.toMap(GoodsBoxDetail::getSpuId, Function.identity(), (key1, key2) -> key2));
                        if (!mapSpu.isEmpty()) {
                            boxMap.putAll(mapSpu);
                        }
                        BigDecimal totalWeight = boxCodeEntry.getValue().stream().map(GoodsBoxDetail::getWeight).reduce(BigDecimal.ZERO, BigDecimal::add);
                        BigDecimal totalVolume = boxCodeEntry.getValue().stream().map(GoodsBoxDetail::getVolume).reduce(BigDecimal.ZERO, BigDecimal::add);
                        weight = weight.add(totalWeight);
                        volume = volume.add(totalVolume);
                        goodsBoxDetailList.addAll(boxCodeEntry.getValue());
                    }
                }
            }
            if (!goodsBoxDetailList.isEmpty() && goodsBoxDetailList.size() > 1) {
                //去重
                LinkedHashMap<Object, Boolean> map1 = new LinkedHashMap<>();
                List<GoodsBoxDetail> downloadListOfUnique = goodsBoxDetailList.stream().filter(i -> map1.putIfAbsent(i.getBoxCode(), Boolean.TRUE) == null).collect(Collectors.toList());
                if (downloadListOfUnique.size() > 1) {
                    //todo 只有一台电脑，原料箱下架不上架，不能三个仓库同时用同一个电脑下，或者存入list放到redis（分拣也可以这样处理）,物品最少的箱子先处理
                    //先发一个，wcs响应，夹取货箱的时候，发第二个指令
                    try {
                        ThreadUtil.sleep(10);
                        boxOutBatch(downloadListOfUnique, clientId);
                    } catch (UtilException e) {
                        log.error("合箱出库异常：{}", e.getMessage());
                        throw new UtilException(e.getMessage());
                    } finally {
                        //downloadListOfUnique每个货箱可能都是不一样的spu，合箱走的是分拣出库流程，N个货箱同时出库的话，可能重量多的先到位
                        //应重量少的先到位，所以放到页面上处理，即到达缓存位后，redis出库中列表和待出库列表都没数据的时候，再发下一个指令
                        //保证同一时间只有一个货箱在执行，分拣流程没有根据重量重的最后出库，所以只能到页面上到位再发指令，保证重量最多的货箱最后最后执行
                        resultList.addAll(goodsBoxDetailList);
                    }
                    break;
                }
            }
        }
    }

    /**
     * 检查货箱编号是否为大货箱，动态处理，防止以后增加货箱，改动太大，尽量改动最小
     *
     * @param goodsBoxDetail
     * @return boolean
     * @author yw
     * @date 2023-11-05 17:12:05
     */
    public boolean checkBoxSizeOfBig(GoodsBoxDetail goodsBoxDetail) {
        String boxCode = goodsBoxDetail.getBoxCode();
        //index为2的区分大小箱：0：大箱，1：小箱
        String boxType = String.valueOf(boxCode.charAt(2));
        //货箱大小类型
        Map<String, String> boxSizeMap = com.lvy.hczn.front.common.constant.Constants.boxSizeMap();
        List<SysDictData> boxSizeList = DictUtils.getDictCache(com.lvy.hczn.front.common.constant.Constants.BOX_SIZE_INFO);
        for (SysDictData sysDictData : boxSizeList) {
            if (boxSizeMap.containsKey(sysDictData.getDictLabel())) {
                if (boxType.equals(sysDictData.getDictValue()) && sysDictData.getDictLabel().equals(com.lvy.hczn.front.common.constant.Constants.BOX_SIZE_BIG_INFO)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 批量出库,只保留最后一个
     * 合箱的时候，最后一个代表是要合到里面的货箱，为了防止提升机就近取而提前出库，放到最后一个，即缓存位没数据的时候再出库
     *
     * @param downloadListOfUnique
     * @param clientId
     * @return void
     * @author yw
     * @date 2022-08-28 19:25:06
     */
    public void boxOutBatch(List<GoodsBoxDetail> downloadListOfUnique, String clientId) throws UtilException {
        log.error("合箱原先顺序：{}", JSONUtil.toJsonStr(downloadListOfUnique));
        List<Object> stringList = downloadListOfUnique.stream().map(GoodsBoxDetail::getBoxCode).collect(Collectors.toList());
        log.error("合箱出库顺序：{}", JSONUtil.toJsonStr(stringList));
        boolean result = redisCache.setAllListOfRight(CacheConstants.SORT_TASK_DOWNLOAD + clientId, ListUtil.sub(stringList, 0, stringList.size() - 1));
        if (result) {
            boolean isTrue = redisCache.setAllListOfRight(CacheConstants.MERGE_BOX + clientId, ListUtil.sub(stringList, stringList.size() - 1, stringList.size()));
            if (isTrue) {
                Param param = new Param();
                UpOrDownPolicy upOrDownPolicy = new UpOrDownPolicy();
                upOrDownPolicy.setClientId(clientId);
                upOrDownPolicy.setBoxType(Constants.GOODS_BOX_TYPE_SPU);
                param.setUpOrDownPolicy(upOrDownPolicy);
                try {
                    //校验，分拣连续发时，且触发货箱离开库位流程，货箱
                    wcsService.sortingOutBatch(param, false);
                } catch (Exception e) {
//                    redisCache.setListOfLeft(CacheConstants.MERGE_BOX + clientId, param.getUpOrDownPolicy().getBoxCode());
                    throw new UtilException("合箱出库忙碌中，请稍后重试！,{}", e);
                }
            } else {
                throw new UtilException("最后一个货箱存放异常！");
            }

        } else {
            throw new UtilException("存放异常！");
        }

    }

    /**
     * 物理删除
     *
     * @param boxCode
     * @return int
     * @author yw
     * @date 2022-07-10 14:15:39
     */
    @Override
    public int delPhysicalByBoxCode(String boxCode) throws Exception {
        if (StrUtil.isEmpty(boxCode)) {
            throw new UtilException("参数为空");
        }
        goodsBoxDetailMapper.delPhysicalByBoxCode(boxCode);
        /*GoodsBox goodsBox = goodsBoxMapper.selectOne(Wrappers.<GoodsBox>lambdaQuery().eq(GoodsBox::getBoxCode, boxCode));
        if (goodsBox != null) {
            if (!Constants.GOODS_BOX_STATE_SIX.equals(goodsBox.getBoxType())) {
                goodsBox.setBoxType(Constants.GOODS_BOX_TYPE_ZERO);
            }
            goodsBox.setWeight(BigDecimal.ZERO);
            goodsBoxMapper.updateById(goodsBox);
        }*/
        return 0;
    }

    /**
     * 查询货箱详情列表
     *
     * @param goodsBoxDetail
     * @return java.util.List<com.lvy.hczn.model.front.GoodsBoxDetail>
     * @author yw
     * @date 2022-07-27 11:28:41
     */
    @Override
    public List<GoodsBoxDetail> selectBoxDetailByBoxCode(GoodsBoxDetail goodsBoxDetail) throws Exception {
        if (goodsBoxDetail == null || StrUtil.isEmpty(goodsBoxDetail.getBoxCode())) {
            throw new UtilException("货箱编号为空");
        }
        log.error("selectBoxDetailByBoxCode，查询货箱详情列表:{}", JSON.toJSONString(goodsBoxDetail));
        List<GoodsBoxDetail> list = goodsBoxDetailMapper.selectBoxDetailByBoxCode(goodsBoxDetail);
        List<GoodsBoxDetail> goodsBoxDetailList = new ArrayList<>();
        GoodsBox goodsBox = goodsBoxMapper.selectOne(Wrappers.<GoodsBox>lambdaQuery().eq(GoodsBox::getBoxCode, goodsBoxDetail.getBoxCode()));
        for (int i = 0; i < list.size(); i++) {
            GoodsBoxDetail goodsBoxDetailInfo = list.get(i);
            /**当前某分详情误差大于货箱误差时，认为有物品，小于货箱误差时，设置为当前详情分拣完毕，重量为0**/
            if (goodsBoxDetailInfo.getWeight().multiply(BigDecimal.valueOf(Constants.WCS_WEIGHT_UNIT_COEFFICIENT)).compareTo(goodsBox.getErrorMax()) > 0) {
                goodsBoxDetailList.add(goodsBoxDetailInfo);
            } else {
                //在误差范围内时，有可能是此物品的每份重量太小，校验一下货箱商品重量跟误差重量的一半作对比，小于每份重量的一半，则默认为空箱
                if (goodsBoxDetailInfo.getWeightOfPerBox() != null && goodsBoxDetailInfo.getWeightOfPerBox().divide(BigDecimal.valueOf(2), 2, RoundingMode.HALF_UP).compareTo(goodsBoxDetailInfo.getWeight()) > 0) {
                    goodsBox.setWeight(goodsBox.getWeight().subtract(goodsBoxDetailInfo.getWeight()));
                    if (goodsBox.getWeight().multiply(BigDecimal.valueOf(Constants.WCS_WEIGHT_UNIT_COEFFICIENT)).compareTo(goodsBox.getErrorMax()) <= 0) {
                        goodsBox.setWeight(BigDecimal.ZERO);
                    }
                    goodsBoxDetailInfo.setWeight(BigDecimal.ZERO);
                    goodsBoxDetailMapper.updateById(goodsBoxDetailInfo);
                    goodsBoxMapper.updateById(goodsBox);
                } else {
                    goodsBoxDetailList.add(goodsBoxDetailInfo);
                }
            }
        }
        return goodsBoxDetailList;
    }

    /**
     * 原料报损
     * 1、更改详情重量，更改货箱总重量
     * 2、校验当前货箱是否满足分拣需求
     *
     * @param sortFrmLoseDto
     * @return void
     * @author yw
     * @date 2022-07-28 15:09:47
     */
    @Override
    @Transactional
    public GoodsBoxDetail updateFrmLoseById(SortFrmLoseDto sortFrmLoseDto) throws Exception {
        if (sortFrmLoseDto == null || StrUtil.isEmpty(sortFrmLoseDto.getGoodsBoxDetailId())) {
            throw new UtilException("参数为空");
        }
        if (sortFrmLoseDto.getWeight() == null) {
            throw new UtilException("重量为空");
        }
        if (StrUtil.isEmpty(sortFrmLoseDto.getClientId())) {
            throw new UtilException("用户信息为空");
        }
        GoodsBoxDetail goodsBoxDetail = goodsBoxDetailMapper.selectById(sortFrmLoseDto.getGoodsBoxDetailId());
        if (goodsBoxDetail != null) {
            log.error("报损货箱详情：{}", JSON.toJSONString(goodsBoxDetail));
            GoodsBox goodsBox = goodsBoxMapper.selectOne(Wrappers.<GoodsBox>lambdaQuery().eq(GoodsBox::getBoxCode, goodsBoxDetail.getBoxCode()));
            log.error("报损货箱：{}", JSON.toJSONString(goodsBox));
            //统计货箱内商品中重量
            List<GoodsBoxDetail> countGoodsDetails = goodsBoxDetailMapper.selectList(Wrappers.<GoodsBoxDetail>lambdaQuery().eq(GoodsBoxDetail::getBoxCode, goodsBoxDetail.getBoxCode()).eq(GoodsBoxDetail::getSpuId, goodsBoxDetail.getSpuId()).eq(GoodsBoxDetail::getSkuCode, goodsBoxDetail.getSkuCode()).eq(GoodsBoxDetail::getSpecCode, goodsBoxDetail.getSpecCode()).gt(GoodsBoxDetail::getWeight, BigDecimal.ZERO));
            BigDecimal frmTotalWeight = countGoodsDetails.stream().map(GoodsBoxDetail::getWeight).reduce(BigDecimal.ZERO, BigDecimal::add);
            if (frmTotalWeight.compareTo(sortFrmLoseDto.getWeight()) < 0) {
                throw new UtilException("报损重量大于库存重量，请检查");
            }

            BigDecimal weight = goodsBoxDetail.getWeight().subtract(sortFrmLoseDto.getWeight()).setScale(2, RoundingMode.HALF_UP);
            //分拣重量的体积
            BigDecimal sortVolume = BigDecimal.ZERO;
            if (weight.compareTo(BigDecimal.ZERO) >= 0 && goodsBoxDetail.getQuantity() > 0) {
                /**标品重量小于每份重量的一半，则清空此条货箱详情**/
                if (goodsBoxDetail.getWeightOfPerBox() == null) {
                    goodsBoxDetail.setWeightOfPerBox(goodsBoxDetail.getWeight().divide(BigDecimal.valueOf(goodsBoxDetail.getQuantity()), 2, RoundingMode.HALF_UP).setScale(2, RoundingMode.HALF_UP));
                }
                BigDecimal bigDecimal = goodsBoxDetail.getWeightOfPerBox().multiply(Constants.HALF_PERCENT).setScale(2, RoundingMode.HALF_UP);
                //获取货箱剩余的每份重量
                BigDecimal perWeight = goodsBoxDetail.getWeight().divide(new BigDecimal(goodsBoxDetail.getQuantity()), 2, RoundingMode.HALF_UP).setScale(2, RoundingMode.HALF_UP);
                /**计算报损重量份数，然后剩余的不够一份的，跟货箱每份重量一半做对比，大于等于一半算一份，小于一半不算一份**/
                BigDecimal num = sortFrmLoseDto.getWeight().divide(perWeight, 2, RoundingMode.HALF_UP);
                //算出分拣重量，减去可以分拣份数的总重量，剩余的重量
                BigDecimal re = sortFrmLoseDto.getWeight().subtract(num.multiply(perWeight)).subtract(perWeight.divide(BigDecimal.valueOf(2)));
                Integer number = num.setScale(0, RoundingMode.HALF_UP).intValue();
                //大于等于一半，算一份
                if (re.compareTo(perWeight) >= 0) {
                    number = number + 1;
                }
                if (goodsBox != null && !goodsBoxDetail.isInBulk() && weight.compareTo(bigDecimal) <= 0) {
                    goodsBoxDetail.setWeight(BigDecimal.ZERO);
                    goodsBoxDetail.setVolume(BigDecimal.ZERO);
                    goodsBoxDetail.setQuantity(0);
                    //标品：货箱剩余重量小于每份重量一半，整个分拣
                    sortFrmLoseDto.setWeight(sortFrmLoseDto.getWeight().add(weight));
                    sortFrmLoseDto.setQuantity(number);
                } else {
                    goodsBoxDetail.setWeight(weight);
                    goodsBoxDetail.setQuantity(goodsBoxDetail.getQuantity() - number);
                    sortFrmLoseDto.setQuantity(number);
                    /**体积计算**/
                    if (goodsBoxDetail.getVolume() != null && goodsBoxDetail.getWeight() != null && goodsBoxDetail.getWeight().compareTo(BigDecimal.ZERO) > 0) {
                        //单位重量的体积
                        BigDecimal perVolume = goodsBoxDetail.getVolume().divide(goodsBoxDetail.getWeight().divide(BigDecimal.valueOf(goodsBoxDetail.getQuantity()), 2, RoundingMode.HALF_UP), 2, RoundingMode.HALF_UP);
                        //报损总体积
                        sortVolume = perVolume.multiply(BigDecimal.valueOf(number));
                        if (goodsBoxDetail.getVolume().subtract(sortVolume).compareTo(BigDecimal.ZERO) >= 0) {
                            goodsBoxDetail.setVolume(goodsBoxDetail.getVolume().subtract(sortVolume));
                        } else {
                            goodsBoxDetail.setVolume(BigDecimal.ZERO);
                        }
                    }
                }
            } else {
                goodsBoxDetail.setQuantity(0);
                goodsBoxDetail.setWeight(BigDecimal.ZERO);
                goodsBoxDetail.setVolume(BigDecimal.ZERO);
                sortFrmLoseDto.setQuantity(0);
            }

            goodsBoxDetailMapper.updateById(goodsBoxDetail);

            if (goodsBox != null) {
                //货箱重量-报损重量
                BigDecimal totalWeight = goodsBox.getWeight().subtract(sortFrmLoseDto.getWeight()).setScale(2, RoundingMode.HALF_UP);
                if (totalWeight.compareTo(BigDecimal.ZERO) > 0) {
                    //部分报损
                    goodsBox.setWeight(totalWeight);
                    goodsBox.setVolume(goodsBox.getVolume().subtract(sortVolume));
                } else {
                    /*存在总重量偶发不匹配详情总重量情况，操作人员暂时无法定位，先这样处理试试看会不会再出现**/
                    if (goodsBoxDetail.getWeight() != null && goodsBoxDetail.getWeight().compareTo(BigDecimal.ZERO) > 0) {
                        List<GoodsBoxDetail> listInfo = super.list(Wrappers.<GoodsBoxDetail>lambdaQuery().eq(GoodsBoxDetail::getBoxCode, goodsBox.getBoxCode()).gt(GoodsBoxDetail::getWeight, BigDecimal.ZERO));
                        if (listInfo != null && !listInfo.isEmpty()) {
                            BigDecimal totalWeightInfo;
                            BigDecimal totalVolumeInfo;
                            totalWeightInfo = listInfo.stream().map(GoodsBoxDetail::getWeight).reduce(BigDecimal.ZERO, BigDecimal::add);
                            totalVolumeInfo = listInfo.stream().map(GoodsBoxDetail::getVolume).reduce(BigDecimal.ZERO, BigDecimal::add);
                            goodsBox.setWeight(totalWeightInfo);
                            goodsBox.setVolume(totalVolumeInfo);
                        }
                    } else {
                        //全部报损
                        goodsBox.setWeight(BigDecimal.ZERO);
                        goodsBox.setVolume(BigDecimal.ZERO);
                    }
                }
                goodsBoxMapper.updateById(goodsBox);
            }

            //生成报损记录
            frmLostRecord(sortFrmLoseDto, goodsBoxDetail);

            //校验是分拣报损还是合箱报损
            dealWithByType(sortFrmLoseDto, goodsBoxDetail);
        }
        return goodsBoxDetail;
    }

    /**
     * 校验是分拣报损还是合箱报损
     *
     * @param sortFrmLoseDto
     * @param goodsBoxDetail
     * @return void
     * @author yw
     * @date 2022-08-28 16:33:22
     */
    public void dealWithByType(SortFrmLoseDto sortFrmLoseDto, GoodsBoxDetail goodsBoxDetail) throws Exception {
        if (sortFrmLoseDto.getType() == 0) {

            if (sortFrmLoseDto.getQuantity() > 0) {
                //报损出库发送到平台
                frmLossSendMsg(sortFrmLoseDto, goodsBoxDetail, sortFrmLoseDto.getDataType());
            }

            //非大件商品，则考虑下架
            if (!sortFrmLoseDto.getBigProduct().equals(com.lvy.hczn.front.common.constant.Constants.GOODS_COLLECT_YES)) {
                //分拣报损，需重出库新货箱
                //校验当前货箱是否满足分拣需求
                dealWithFrmLose(goodsBoxDetail, sortFrmLoseDto);
            }
        } else if (sortFrmLoseDto.getType() == 1) {
            //合箱报损
            //更改redis重量，防止刷新展示错误数据
            List<Object> objectList = redisCache.getList(CacheConstants.MERGE_BOX_DETAIL_LIST + sortFrmLoseDto.getClientId(), 0, -1);
            if (objectList != null) {
                //报损出库发送到平台
                frmLossSendMsg(sortFrmLoseDto, goodsBoxDetail, sortFrmLoseDto.getDataType());

                List<Object> objects = new ArrayList<>();
                for (Object object : objectList) {
                    GoodsBoxDetail goodsBoxDetail1 = JSONUtil.toBean(JSONUtil.toJsonStr(object), GoodsBoxDetail.class);
                    if (goodsBoxDetail1.getId().equals(goodsBoxDetail.getId())) {
                        goodsBoxDetail1.setWeight(goodsBoxDetail.getWeight());
                    }
                    objects.add(goodsBoxDetail1);
                }
                redisCache.del(CacheConstants.MERGE_BOX_DETAIL_LIST + sortFrmLoseDto.getClientId());
                if (!objects.isEmpty()) {
                    redisCache.setAllListOfRight(CacheConstants.MERGE_BOX_DETAIL_LIST + sortFrmLoseDto.getClientId(), objects);
                }
            }
        }
    }

    /**
     * 报损记录
     *
     * @param sortFrmLoseDto
     * @param goodsBoxDetail
     * @return void
     * @author yw
     * @date 2022-08-23 11:56:15
     */
    public void frmLostRecord(SortFrmLoseDto sortFrmLoseDto, GoodsBoxDetail goodsBoxDetail) {
        SpuSortRecord record = new SpuSortRecord();
        Spu spu = spuService.getById(goodsBoxDetail.getSpuId());
        if (spu != null) {
            record.setSpuCode(spu.getSpuCode());
        }
        record.setSpuId(goodsBoxDetail.getSpuId());
        record.setSkuCode(goodsBoxDetail.getSkuCode());
        record.setSpecCode(goodsBoxDetail.getSpecCode());
        record.setBoxCode(goodsBoxDetail.getBoxCode());
        record.setType(Constants.SPU_SORT_RECORD_TYPE_ONE);
        record.setWeight(sortFrmLoseDto.getWeight());
        record.setUnit(goodsBoxDetail.getUnit());
        record.setSupplierCode(goodsBoxDetail.getSupplierCode());
        record.setCreateBy(ShiroUtils.getLoginName());
        record.setSupplierCode(goodsBoxDetail.getSupplierCode());
        record.setProducerId(goodsBoxDetail.getProducerId());
        record.setProductDate(goodsBoxDetail.getProduceDate());
        record.setWarrantyDate(goodsBoxDetail.getWarrantyDate());
        record.setDataType(com.lvy.hczn.front.common.constant.Constants.WAREHOUSING_ENTRY_DATA_TYPE_TEN);
        record.setTreated(false);
        String result = DictUtils.getDictValue(com.lvy.hczn.front.common.constant.Constants.SYS_DICT_FRM_LOST, com.lvy.hczn.front.common.constant.Constants.SYS_DICT_FRM_LOST_BREAKAGE);
        if (!StrUtil.isEmpty(result)) {
            record.setRemark(result);
        }
        record.setStatus(com.lvy.hczn.front.common.constant.Constants.FRM_LOSS_DATA_TYPE_ZERO);
        spuSortRecordService.save(record);
        /*if (spuSortRecordService.save(record)) {
            Warehouse warehouse = warehouseService.selectLocalWarehouseFront();
            if (warehouse != null) {
                ParamInfo<SpuSortRecord> info = new ParamInfo<>();
                info.setWarehouseCode(warehouse.getWarehouseCode());
                info.setData(record);
                String topic = warehouseService.selectRegionAreaTopic(warehouse);
                String tag = MqConstants.TOPIC_WAREHOUSE_FRMLOST_TAG;
                try {
                    producerUtil.sendAsyncMsg(topic, tag, JSON.toJSONString(info).getBytes(StandardCharsets.UTF_8), IdUtil.fastSimpleUUID());
                } catch (Exception e) {
                    throw new UtilException("报损信息发送异常");
                }
            }
        }*/
    }

    /**
     * 需同步到平台的报损出库信息
     *
     * @param sortFrmLoseDto
     * @param goodsBoxDetail
     * @param dataType
     * @return void
     * @author yw
     * @date 2023-09-16 17:28:33
     */
    public void frmLossSendMsg(SortFrmLoseDto sortFrmLoseDto, GoodsBoxDetail goodsBoxDetail, String dataType) {
        Warehouse warehouse = redisCache.getCacheObject(RedisConstants.WAREHOUSE_WMS);
        if (warehouse == null) {
            throw new UtilException("缓存内请先配置仓库");
        }
        WarehousingEntry warehouseEntry = new WarehousingEntry();
        warehouseEntry.setEntryCode(goodsBoxDetail.getBatchCode());
        warehouseEntry.setServiceCode(warehouseEntry.getEntryCode());
        warehouseEntry.setServiceType(com.lvy.hczn.front.common.constant.Constants.WAREHOUSE_ENTRY_OUT_TYPE_SEVEN);
        warehouseEntry.setDataType(dataType);
        warehouseEntry.setWarehouseCode(warehouse.getWarehouseCode());
        warehouseEntry.setBoxCode(goodsBoxDetail.getBoxCode());
        warehouseEntry.setSpuId(goodsBoxDetail.getSpuId());
        warehouseEntry.setSkuCode(goodsBoxDetail.getSkuCode());
        warehouseEntry.setSpecCode(goodsBoxDetail.getSpecCode());
        warehouseEntry.setBatchCode(warehouseEntry.getEntryCode());
        warehouseEntry.setPrice(BigDecimal.ZERO);
        if (sortFrmLoseDto.getQuantity() == null || sortFrmLoseDto.getQuantity() == 0) {
            warehouseEntry.setQuantity(1);
        } else {
            warehouseEntry.setQuantity(sortFrmLoseDto.getQuantity());
        }
        warehouseEntry.setWeight(sortFrmLoseDto.getWeight());
        warehouseEntry.setWeightDone(true);
        warehouseEntry.setUnit(goodsBoxDetail.getUnit());
        warehouseEntry.setWeightOfPerBox(BigDecimal.ZERO);
        warehouseEntry.setNumberOfPerBox(0);
        warehouseEntry.setSupplierCode(goodsBoxDetail.getSupplierCode());
        warehouseEntry.setProducerId(goodsBoxDetail.getProducerId());
        warehouseEntry.setWarrantyDate(goodsBoxDetail.getWarrantyDate());
        warehouseEntry.setPickTime(goodsBoxDetail.getProduceDate());
        if (warehouseEntry.getPickTime() != null && warehouseEntry.getWarrantyDate() != null) {
            warehouseEntry.setShelfLife(warehouseEntry.getPickTime().plusDays(warehouseEntry.getWarrantyDate()));
        }
        warehouseEntry.setStatus(Constants.ENTRY_ING);
        warehouseEntry.setSourceId(warehouse.getWarehouseCode());
        if (warehousingEntryService.save(warehouseEntry)) {
            warehousingEntryService.commitToPlatform(warehouseEntry, true, false);
        }
    }

    /**
     * 根据指令状态和流程类型查询货箱信息
     *
     * @param type
     * @param processType
     * @return java.util.List<com.lvy.hczn.model.front.GoodsBoxDetail>
     * @author yw
     * @date 2022-08-22 19:55:14
     */
    @Override
    public List<GoodsBoxDetail> selectByUpOrDownPolicyType(String type, Integer processType) throws Exception {
        if (StrUtil.isEmpty(type)) {
            throw new UtilException("类型为空");
        }
        if (processType == null) {
            throw new UtilException("流程为空");
        }
        return goodsBoxDetailMapper.selectByUpOrDownPolicyType(type, processType);
    }

    /**
     * 根据出库单查询货箱详情列表
     *
     * @param outBoundOrderCode
     * @return
     */
    @Override
    public List<GoodsBoxDetail> selectTransferSortDoneByOutBoundOrderCode(String outBoundOrderCode) {
        if (StrUtil.isEmpty(outBoundOrderCode)) {
            throw new UtilException("出库单号为空");
        }
        return goodsBoxDetailMapper.selectTransferSortDoneByOutBoundOrderCode(outBoundOrderCode);
    }

    /**
     * 临期报损，离开缓存位逻辑处理
     *
     * @param goodsBoxDetailInfo
     * @return void
     * @author yw
     * @date 2023-09-22 20:50:06
     */
    @Override
    public void frmLossLeaveBuffer(GoodsBoxDetail goodsBoxDetailInfo) {
        if (StrUtil.isEmpty(goodsBoxDetailInfo.getBoxCode())) {
            throw new UtilException("临期离开缓存位货箱编号为空");
        }
        String key = CacheConstants.CACHE_FRM_LOSS + goodsBoxDetailInfo.getBoxCode();
        List<String> boxDetailIdList = redisCache.getCacheList(key);
        List<GoodsBoxDetail> goodsBoxDetailList = goodsBoxDetailMapper.selectBoxDetailByBoxCode(goodsBoxDetailInfo);
        if (boxDetailIdList == null || boxDetailIdList.isEmpty()) {
            goodsBoxDetailList.forEach(detail -> {
                detail.setWeight(BigDecimal.ZERO);
                detail.setQuantity(0);
                detail.setVolume(BigDecimal.ZERO);
            });
        } else {
            Map<String, String> map = boxDetailIdList.stream().collect(Collectors.toMap(item -> String.valueOf(item), item -> item.toString()));
            goodsBoxDetailList.forEach(detail -> {
                if (!map.containsKey(detail.getId())) {
                    goodsBoxDetailMapper.deleteById(detail.getId());
                }
            });
        }
    }

    /**
     * 报损处理后
     * <p>
     * 1、校验当前箱子箱子的spu重量，是否够分拣
     * 2、不够，则查询待发送指令的redis中查询重量是否够分拣(需查出库和待出库的key)
     * 3、不够则发送指令出库
     * 4、够则中断步骤
     *
     * @param goodsBoxDetail
     * @param sortFrmLoseDto
     * @return void
     * @author yw
     * @date 2022-07-28 16:08:17
     */
    public void dealWithFrmLose(GoodsBoxDetail goodsBoxDetail, SortFrmLoseDto sortFrmLoseDto) throws Exception {
        //当前货箱需分拣的任务列表
        if (sortFrmLoseDto.getSortTaskDetails() != null && !sortFrmLoseDto.getSortTaskDetails().isEmpty()) {
//                List<GoodsBoxDetail> list = goodsBoxDetailMapper.selectList(Wrappers.<GoodsBoxDetail>lambdaQuery().eq(GoodsBoxDetail::getBoxCode, goodsBoxDetail.getBoxCode()));
            Map<String, List<SortTaskDetail>> sortDetailMap = sortFrmLoseDto.getSortTaskDetails().stream().collect(Collectors.groupingBy(it -> it.getSpuId()));
            if (sortDetailMap != null) {
                //获取当前报损的物品的分拣任务的总重量
                List<SortTaskDetail> sortTaskDetails = sortDetailMap.get(goodsBoxDetail.getSpuId());
                if (sortTaskDetails != null && !sortTaskDetails.isEmpty()) {
                    BigDecimal sortWeight = BigDecimal.ZERO;
                    //计算当前分拣箱子需要分拣的同一物品的总重量
                    for (SortTaskDetail sortTaskDetail : sortTaskDetails) {
                        sortWeight = sortWeight.add(sortTaskDetail.getWeight().multiply(BigDecimal.valueOf(sortTaskDetail.getQuantity() - sortTaskDetail.getSortQuantity())));
                    }
                    //当前到位货箱详情的相同spu物品减去报损重量后，不够分拣任务时，查询待发送出库指令的redis是否够
                    if (goodsBoxDetail.getWeight().compareTo(sortWeight) < 0) {
                        //todo 分拣页面，原料下的分拣列表，展示同一个spu所有的分拣任务，不管到位货箱原料够不够，
                        // 这样reWeight就是待分拣总重量，方便比较redis待发指令的同一个spu货箱重量是否能够满足
                        //剩余分拣重量
                        BigDecimal reWeight = sortWeight.subtract(goodsBoxDetail.getWeight());

                        //从已发到wcs的货箱中校验，不够，再去查待出库的
                        List<GoodsBoxDetail> boxDetailList = selectByUpOrDownPolicyType(Constants.BOX_OPERATION_DOWN, Constants.STATE_PROCESS_TYPE_ONE);
                        if (boxDetailList != null && !boxDetailList.isEmpty()) {
                            log.error("出库中的货箱：{}", JSONUtil.toJsonStr(boxDetailList));
                            BigDecimal downloadingSpuWeight = boxDetailList.stream().filter(item -> item.getSpuId().equals(goodsBoxDetail.getSpuId())).map(GoodsBoxDetail::getWeight).reduce(BigDecimal.ZERO, BigDecimal::add);
                            reWeight = reWeight.subtract(downloadingSpuWeight);
                        }

                        if (reWeight.compareTo(BigDecimal.ZERO) > 0) {
                            //从出库列表中查询
                            List<Object> objectsDownload = redisCache.getList(CacheConstants.SORT_TASK_DOWNLOAD + sortFrmLoseDto.getClientId(), 0, -1);
                            if (objectsDownload != null && !objectsDownload.isEmpty()) {
                                /**查询出库列表中，是否有当前报损的spu，有，则校验是否够分拣**/
                                for (Object object : objectsDownload) {
                                    String boxCode = String.valueOf(object);
                                    GoodsBoxDetail detail = new GoodsBoxDetail();
                                    detail.setBoxCode(boxCode);
                                    List<GoodsBoxDetail> goodsBoxDetailList = goodsBoxDetailMapper.selectBoxDetailByBoxCode(detail);
                                    for (GoodsBoxDetail item : goodsBoxDetailList) {
                                        if (item.getSpuId().equals(goodsBoxDetail.getSpuId())) {
                                            reWeight = reWeight.subtract(item.getWeight());
                                        }
                                        if (reWeight.compareTo(BigDecimal.ZERO) <= 0) {
                                            break;
                                        }
                                    }
                                }
                                //报损的当前spu重量，在出库列表中，不满足，则从待出库列表中查询，也不满足，则再出库货箱
                                if (reWeight.compareTo(BigDecimal.ZERO) > 0) {
                                    reDownloadBox(goodsBoxDetail, sortFrmLoseDto, reWeight, false);
                                }
                            } else {
                                reDownloadBox(goodsBoxDetail, sortFrmLoseDto, reWeight, true);
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * 从待出库列表中查询当前需分拣重量，再报损后，是否还够分拣
     *
     * @param goodsBoxDetail
     * @param sortFrmLoseDto
     * @param reWeight
     * @return void
     * @author yw
     * @date 2022-08-14 12:25:19
     */
    public void reDownloadBox(GoodsBoxDetail goodsBoxDetail, SortFrmLoseDto sortFrmLoseDto, BigDecimal reWeight, boolean isNullDownload) throws Exception {
        //从待出库列表中查询
        List<Object> objects = redisCache.getList(CacheConstants.SORT_TASK_DOWNLOAD_LAST + sortFrmLoseDto.getClientId(), 0, -1);
        if (objects != null && !objects.isEmpty()) {
            for (Object object : objects) {
                GoodsBoxDetail boxDetail = JSONUtil.toBean(JSONUtil.toJsonStr(object), GoodsBoxDetail.class);
                //获取当前货箱，所有物品，因为boxDetail不一定就是当前报损的物品
                List<GoodsBoxDetail> goodsBoxDetails = goodsBoxDetailMapper.selectBoxDetailByBoxCode(boxDetail);
                if (goodsBoxDetails != null && !goodsBoxDetails.isEmpty()) {
                    for (GoodsBoxDetail detail : goodsBoxDetails) {
                        if (detail.getSpuId().equals(goodsBoxDetail.getSpuId()) && detail.getSkuCode().equals(goodsBoxDetail.getSkuCode())) {
                            reWeight = reWeight.subtract(detail.getWeight());
                        }
                        if (reWeight.compareTo(BigDecimal.ZERO) <= 0) {
                            break;
                        }
                    }
                } else {
                    if (boxDetail.getSpuId().equals(goodsBoxDetail.getSpuId()) && boxDetail.getSkuCode().equals(goodsBoxDetail.getSkuCode())) {
                        reWeight = reWeight.subtract(boxDetail.getWeight());
                    }
                }
                if (reWeight.compareTo(BigDecimal.ZERO) <= 0) {
                    break;
                }
            }
            if (reWeight.compareTo(BigDecimal.ZERO) > 0) {
                //发送出库指令
                dealWithDownloadBox(reWeight, goodsBoxDetail, sortFrmLoseDto, isNullDownload, false);
            }
        } else {
            //直接发送出库指令
            dealWithDownloadBox(reWeight, goodsBoxDetail, sortFrmLoseDto, isNullDownload, true);
        }
    }

    /*public void dealWithDownloadFromRedis(BigDecimal reWeight, GoodsBoxDetail goodsBoxDetail, SortFrmLoseDto sortFrmLoseDto) throws Exception {
        List<Object> objects = redisCache.getList(CacheConstants.SORT_TASK_DOWNLOAD_LAST + sortFrmLoseDto.getClientId(), 0, -1);
        if (objects != null && !objects.isEmpty()) {
            for (Object object : objects) {
                GoodsBoxDetail boxDetail = JSONUtil.toBean(JSONUtil.toJsonStr(object), GoodsBoxDetail.class);
                if (boxDetail.getSpuId().equals(goodsBoxDetail.getSpuId())) {
                    reWeight = reWeight.subtract(boxDetail.getWeight());
                }
            }
            if (reWeight.compareTo(BigDecimal.ZERO) > 0) {
                //发送出库指令
                dealWithDownloadBox(reWeight, goodsBoxDetail, sortFrmLoseDto);
            }
        } else {
            //直接发送出库指令
            dealWithDownloadBox(reWeight, goodsBoxDetail, sortFrmLoseDto);
        }
    }*/

    /**
     * 获取仓库货箱数据后，校验需要下的N个货箱，同时发送出库指令
     * 1.获取在途货箱及在库位货箱
     * 2.在途货箱无需出库
     *
     * @param downloadWeight
     * @param goodsBoxDetail
     * @param sortFrmLoseDto
     * @return void
     * @author yw
     * @date 2022-07-28 20:29:27
     */
    public void dealWithDownloadBox(BigDecimal downloadWeight, GoodsBoxDetail goodsBoxDetail, SortFrmLoseDto sortFrmLoseDto, boolean isNullDownload, boolean isNullDownloadLast) throws Exception {
        List<GoodsBoxDetail> list = selectBySpuIdList(ListUtil.toList(goodsBoxDetail.getSpuId()), sortFrmLoseDto.getClientId());
        if (list != null) {
            List<GoodsBoxDetail> detailList = new ArrayList<>();
            for (GoodsBoxDetail boxDetail : list) {
                if (boxDetail.getSpuId().equals(goodsBoxDetail.getSpuId()) && boxDetail.getSkuCode().equals(goodsBoxDetail.getSkuCode())) {
                    downloadWeight = downloadWeight.subtract(boxDetail.getWeight());
                    if (downloadWeight.compareTo(BigDecimal.ZERO) > 0) {
                        detailList.add(boxDetail);
                    } else {
                        detailList.add(boxDetail);
                        break;
                    }
                }
            }
            /**
             * 校验货箱是否已在redis的出库中，或者待出库中的列表中，只要有一边存在，则无须再放入其中
             * **/
            if (!detailList.isEmpty()) {

                log.error("报损出库信息：{}", JSONUtil.toJsonStr(detailList));
                //去重
                LinkedHashMap<Object, Boolean> map = new LinkedHashMap<>();
                List<GoodsBoxDetail> downloadListOfUniqueByCode = detailList.stream().filter(i -> map.putIfAbsent(i.getBoxCode(), Boolean.TRUE) == null).collect(Collectors.toList());

                log.error("报损出库去重信息：{}", JSONUtil.toJsonStr(downloadListOfUniqueByCode));

                List<Object> downloadListOfUnique = new ArrayList<>();
                for (GoodsBoxDetail boxDetail : downloadListOfUniqueByCode) {
                    //在仓库货箱，则放到redis,在途货箱则忽略
                    if (boxDetail.getBoxStatus().equals(Constants.GOODS_BOX_STATE_THREE)) {
                        downloadListOfUnique.add(boxDetail);
                    }
                }

                if (!downloadListOfUnique.isEmpty()) {
                    /**相同spu分拣，先放一个到发送指令的list中，等到分拣完毕，离开缓存位时，再从待发送指令list中获取一个发送指令出库分拣**/
                    //todo 当isNullDownload，isNullDownloadLast查出为空后，同时分拣任务会向SORT_TASK_DOWNLOAD，SORT_TASK_DOWNLOAD_LAST注入值，也有可能存在重复情况
                    if (isNullDownload) {
                        redisCache.setListOfRight(CacheConstants.SORT_TASK_DOWNLOAD + sortFrmLoseDto.getClientId(), JSONUtil.toBean(JSONUtil.toJsonStr(downloadListOfUnique.get(0)), GoodsBoxDetail.class).getBoxCode());
                        if (downloadListOfUnique.size() > 1) {
                            List<Object> lastList = ListUtil.sub(downloadListOfUnique, 1, downloadListOfUnique.size());
                            if (isNullDownloadLast) {
                                redisCache.setAllListOfLeft(CacheConstants.SORT_TASK_DOWNLOAD_LAST + sortFrmLoseDto.getClientId(), lastList);
                            } else {
                                //待出库list为非空，则校验是否存在，不存在则放入
                                List<Object> objects = redisCache.getList(CacheConstants.SORT_TASK_DOWNLOAD_LAST + sortFrmLoseDto.getClientId(), 0, -1);
                                for (Object o : lastList) {
                                    GoodsBoxDetail goodsBoxDetailInfo = JSONUtil.toBean(JSONUtil.toJsonStr(o), GoodsBoxDetail.class);
                                    boolean flag = false;
                                    for (Object object : objects) {
                                        GoodsBoxDetail boxDetail = JSONUtil.toBean(JSONUtil.toJsonStr(object), GoodsBoxDetail.class);
                                        if (goodsBoxDetailInfo.equals(boxDetail.getBoxCode())) {
                                            flag = true;
                                            break;
                                        }
                                    }
                                    if (!flag) {
                                        log.error("报损放入SORT_TASK_DOWNLOAD_LAST_0：{}", goodsBoxDetailInfo);
                                        redisCache.setListOfRight(CacheConstants.SORT_TASK_DOWNLOAD_LAST + sortFrmLoseDto.getClientId(), goodsBoxDetailInfo);
                                    }
                                }
                            }
                        }
                    } else {
//                        LinkedHashMap<String, List<GoodsBoxDetail>> goodsDetailMap = downloadListOfUnique.stream().collect(Collectors.groupingBy(GoodsBoxDetail::getBoxCode, LinkedHashMap::new, Collectors.toList()));
                        //出库中列表
                        List<Object> objects = redisCache.getList(CacheConstants.SORT_TASK_DOWNLOAD + sortFrmLoseDto.getClientId(), 0, -1);
                        List<Object> notInDownload = new ArrayList<>();
                        for (Object o : downloadListOfUnique) {
                            GoodsBoxDetail goodsBoxDetailInfo = JSONUtil.toBean(JSONUtil.toJsonStr(o), GoodsBoxDetail.class);
                            boolean flag = false;
                            for (Object object : objects) {
                                String boxCode = String.valueOf(object);
                                if (goodsBoxDetailInfo.getBoxCode().equals(boxCode)) {
                                    flag = true;
                                    break;
                                }
                            }
                            if (!flag) {
                                notInDownload.add(goodsBoxDetailInfo);
                            }
                        }

                        if (!notInDownload.isEmpty()) {
                            if (isNullDownloadLast) {
                                redisCache.setAllListOfLeft(CacheConstants.SORT_TASK_DOWNLOAD_LAST + sortFrmLoseDto.getClientId(), notInDownload);
                            } else {
                                List<Object> objectList = redisCache.getList(CacheConstants.SORT_TASK_DOWNLOAD_LAST + sortFrmLoseDto.getClientId(), 0, -1);
                                for (Object object : notInDownload) {
                                    boolean flag = false;
                                    GoodsBoxDetail goodsBoxDetail1 = JSONUtil.toBean(JSONUtil.toJsonStr(object), GoodsBoxDetail.class);
                                    for (Object o : objectList) {
                                        GoodsBoxDetail boxDetail = JSONUtil.toBean(JSONUtil.toJsonStr(o), GoodsBoxDetail.class);
                                        if (goodsBoxDetail1.getBoxCode().equals(boxDetail.getBoxCode())) {
                                            flag = true;
                                            break;
                                        }
                                    }
                                    //不在出空中的列表中，且不在待出库列表中，则放入待出库列表中
                                    if (!flag) {
                                        log.error("报损放入SORT_TASK_DOWNLOAD_LAST_1：{}", JSONUtil.toJsonStr(goodsBoxDetail1));
                                        redisCache.setListOfRight(CacheConstants.SORT_TASK_DOWNLOAD_LAST + sortFrmLoseDto.getClientId(), goodsBoxDetail1);
                                    }
                                }
                            }
                        }
                    }
                }

                //如果报损，就出库的话，同一个货箱，如果报损多个，报损第一个的时候，发送出库指令的话，如果第二个物品也是在同一个货箱中，则不会去校验这一出库的货箱是否包含该物品，应在离开缓存位的时候再出库
                /*ThreadUtil.execAsync(() -> {
                    Param param = new Param();
                    param.setUpOrDownPolicy(new UpOrDownPolicy());
                    param.getUpOrDownPolicy().setClientId(sortFrmLoseDto.getClientId());
                    //校验，分拣连续发时，且触发货箱离开库位流程，货箱
                    try {
                        wcsService.sortingOutBatch(param);
                    } catch (Exception e) {
                        log.error("dealWithDownloadBox->sortingOutBatch,{}", e);
                        e.printStackTrace();
                    }
                });*/
            }
        }
    }


    /**
     * 指定仓库编号商品ID查询列表
     *
     * @param warehouseNumber
     * @param spuIdList
     * @return java.util.List<com.lvy.hczn.front.system.domain.business.GoodsBox>
     * @author yw
     * @date 2023-11-03 10:34:56
     */
    @Override
    public List<GoodsBoxDetail> selectByWareHouseNumberAndSpuIdList(String warehouseNumber, List<String> spuIdList) {
        if (StrUtil.isEmpty(warehouseNumber) || spuIdList.isEmpty()) {
            throw new UtilException("仓库编号或商品列表参数为空");
        }
        return goodsBoxDetailMapper.selectByWareHouseNumberAndSpuIdList(warehouseNumber, spuIdList);
    }

    @Override
    public GoodsBoxDetail selectGoodsBoxDetailById(String id) {
        if (StrUtil.isEmpty(id)) {
            throw new UtilException("参数为空");
        }
        return goodsBoxDetailMapper.selectById(id);
    }

    @Override
    public List<GoodsBoxDetail> selectGoodsBoxDetailList(GoodsBoxDetail goodsBoxDetail) {
        return goodsBoxDetailMapper.selectGoodsBoxDetailList(goodsBoxDetail);
    }

    @Override
    public int insertGoodsBoxDetail(GoodsBoxDetail goodsBoxDetail) {
        return goodsBoxDetailMapper.insert(goodsBoxDetail);
    }

    @Override
    public int updateGoodsBoxDetail(GoodsBoxDetail goodsBoxDetail) {
        return goodsBoxDetailMapper.updateById(goodsBoxDetail);
    }

    @Override
    public int deleteGoodsBoxDetailByIds(String ids) {
        String[] arrId = Convert.toStrArray(ids);
        for (String id : arrId) {
            goodsBoxDetailMapper.deleteById(id);
        }
        return arrId.length;
    }

    @Override
    public int deleteGoodsBoxDetailById(String id) {
        return goodsBoxDetailMapper.deleteById(id);
    }

    /**
     * @Description 获取选中商品，查询货架内所在仓库的温区是否有空货箱，然后则选中一个空货箱，注：只允许一台电脑操作
     * @Param
     * @Author yw
     * @Date 2024/7/1 15:59
     * @Return
     **/
    @Override
    public synchronized ModelMap bigProductEntryOpen(HttpServletRequest request, String ids, ModelMap mmap) {
        String clientId = clientId(request, mmap);
        if (StrUtil.isEmpty(clientId) || Integer.parseInt(clientId) < 0) {
            throw new UtilException("请用货架分拣电脑处理");
        }
        String result = redisCache.getCacheObject(RedisConstants.BIG_PRODUCT_ENTRY);
        if (!StrUtil.isEmpty(result)) {
            /*只允许一台电脑操作**/
            JSONArray array = (JSONArray) redisCache.get("sys_dict:wh_com");
            String resultClient = "";
            String doClientId = "";
            if (array != null && !array.isEmpty()) {
                for (int i = 0; i < array.size(); i++) {
                    SysDictData data = (SysDictData) array.get(i);
                    resultClient = redisCache.getCacheObject(RedisConstants.BIG_PRODUCT_ENTRY + ":" + data.getDictValue());
                    if (!StrUtil.isEmpty(resultClient)) {
                        doClientId = data.getDictValue();
                        break;
                    }
                }
            }
            if (!clientId.equals(doClientId)) {
                throw new UtilException(doClientId + "号仓库正在操作中，请稍后");
            }
        }
        if (StrUtil.isEmpty(ids)) {
            throw new UtilException("参数为空");
        }
        //虚拟货箱
        String vBoxCode = DictUtils.getDictValue(com.lvy.hczn.front.common.constant.Constants.VIRTUALLY_BOX_GOODS, com.lvy.hczn.front.common.constant.Constants.VIRTUALLY_BOX_GOODS_BOX_CODE);
        if (StrUtil.isEmpty(vBoxCode)) {
            throw new UtilException("数据字典未配置虚拟货箱");
        }
        String[] arrId = Convert.toStrArray(ids);
        List<GoodsBoxDetail> goodsBoxDetailList = new ArrayList<>();
        Map<String, String> map = new HashMap<>();
        /*相同商品**/
        for (String id : arrId) {
            GoodsBoxDetail goodsBoxDetail = super.getById(id);
            if (goodsBoxDetail != null) {
                if (!goodsBoxDetail.getBoxCode().equals(vBoxCode)) {
                    throw new UtilException("选中商品为非大件商品，没有放在地平仓");
                }
                if (map.isEmpty() || map.containsKey(goodsBoxDetail.getSpuId() + goodsBoxDetail.getSkuCode() + goodsBoxDetail.getSpecCode())) {
                    map.put(goodsBoxDetail.getSpuId() + goodsBoxDetail.getSkuCode() + goodsBoxDetail.getSpecCode(), goodsBoxDetail.getSpuId() + goodsBoxDetail.getSkuCode() + goodsBoxDetail.getSpecCode());
                    goodsBoxDetailList.add(goodsBoxDetail);
                } else {
                    throw new UtilException("请选择相同商品");
                }
            } else {
                throw new UtilException("查询信息为空");
            }
        }

        //根据温区查询温区下已上架的空箱
        if (!goodsBoxDetailList.isEmpty()) {
            GoodsBoxDetail goodsBoxDetail = goodsBoxDetailList.get(0);
            List<GoodsBoxDetail> GoodsBoxDetails = super.list(Wrappers.<GoodsBoxDetail>lambdaQuery().eq(GoodsBoxDetail::getBoxCode, goodsBoxDetail.getBoxCode()).eq(GoodsBoxDetail::getSpuId, goodsBoxDetail.getSpuId()).eq(GoodsBoxDetail::getSkuCode, goodsBoxDetail.getSkuCode()).eq(GoodsBoxDetail::getSpecCode, goodsBoxDetail.getSpecCode()).eq(GoodsBoxDetail::getSupplierCode, goodsBoxDetail.getSupplierCode()));
            Spu spu = spuService.getById(goodsBoxDetail.getSpuId());
            if (spu == null || spu.getTemperatureZoneCode() == null) {
                throw new UtilException("无商品信息或者商品的温区为空");
            }
            //查询温区下已上架的空货箱
            GoodsBox goodsBox = goodsBoxMapper.selectOne(Wrappers.<GoodsBox>lambdaQuery().eq(GoodsBox::getStatus, Constants.GOODS_BOX_STATE_THREE).eq(GoodsBox::getBoxType, Constants.GOODS_BOX_TYPE_ZERO).eq(GoodsBox::getActTemperatureCode, spu.getTemperatureZoneCode()).last("limit 1"));
            if (goodsBox == null || StrUtil.isEmpty(goodsBox.getId())) {
                throw new UtilException("温区:" + spu.getTemperatureZoneCode() + "没有空箱");
            }
            BigDecimal totalWeight = GoodsBoxDetails.stream().map(GoodsBoxDetail::getWeight).reduce(BigDecimal.ZERO, BigDecimal::add);
            BigDecimal totalVolume = GoodsBoxDetails.stream().map(GoodsBoxDetail::getVolume).reduce(BigDecimal.ZERO, BigDecimal::add);
            goodsBox.setDetailList(GoodsBoxDetails);
            goodsBox.setWeight(totalWeight);
            goodsBox.setVolume(totalVolume);
            List<String> idList = GoodsBoxDetails.stream().map(GoodsBoxDetail::getId).collect(Collectors.toList());
            String str = StringUtils.collectionToDelimitedString(idList, ",");
            goodsBox.setIds(str);
            goodsBox.setClientId(clientId);
            mmap.put("goodsBox", goodsBox);
            mmap.put("ids", ids);
            redisCache.set(RedisConstants.BIG_PRODUCT_ENTRY, "true");
            redisCache.set(RedisConstants.BIG_PRODUCT_ENTRY + ":" + clientId, clientId);
        } else {
            throw new UtilException("无相关商品详情");
        }
        return mmap;
    }

    /**
     * @Description 大件物品转入货架
     * @Param
     * @Author yw
     * @Date 2024/7/1 15:59
     * @Return
     **/
    @Override
    public GoodsBox bigProductEntry(GoodsBox goodsBox) {
        if (StrUtil.isEmpty(goodsBox.getId())) {
            throw new UtilException("ID为空");
        }
        goodsBox.setBoxType(Constants.GOODS_BOX_TYPE_ONE);
        if (goodsBoxMapper.updateById(goodsBox) == 1) {
            String[] arrId = Convert.toStrArray(goodsBox.getIds());
            for (String id : arrId) {
                GoodsBoxDetail goodsBoxDetail = new GoodsBoxDetail();
                goodsBoxDetail.setId(id);
                goodsBoxDetail.setBoxCode(goodsBox.getBoxCode());
                super.updateById(goodsBoxDetail);
            }
            GoodsBox goodsBoxInfo = goodsBoxMapper.selectById(goodsBox.getId());
            if (goodsBoxInfo == null) {
                throw new UtilException("货箱信息为空");
            }
            if (StrUtil.isEmpty(goodsBoxInfo.getActTemperatureCode()) && goodsBoxInfo.getTemperatureZoneCode() == null) {
                throw new UtilException("温区信息为空");
            }
            redisCache.del(RedisConstants.BIG_PRODUCT_ENTRY);
            redisCache.del(RedisConstants.BIG_PRODUCT_ENTRY + ":" + goodsBox.getClientId());
            goodsBox.setActTemperatureCode(goodsBoxInfo.getActTemperatureCode());
            goodsBox.setTemperatureZoneCode(goodsBoxInfo.getTemperatureZoneCode());
            goodsBox.setGoodsBoxSpecId(goodsBoxInfo.getGoodsBoxSpecId());
            goodsBox.setIntStorage(true);
            warehousingEntryService.boxShelfLocationMsgSingle(goodsBox, true);
        }
        return null;
    }

    public String clientId(HttpServletRequest request, ModelMap mmap) {
        String ip = ServletUtil.getClientIP(request, null);
        String clientId = "";
        JSONArray array = (JSONArray) redisCache.get("sys_dict:wh_com");
        if (array != null && !array.isEmpty()) {
            for (int i = 0; i < array.size(); i++) {
                SysDictData data = (SysDictData) array.get(i);
                if (data.getDictLabel().equals(ip)) {
                    mmap.put("clientId", data.getDictValue());
                    clientId = data.getDictValue();
                    break;
                }
            }
        }
        return clientId;
    }
}
